use crate::mqi;
use std::ffi::{c_void};
use std::os::raw::{c_char};
use crate::{MQError, Result,
            strcpy,
            set_mqgmo_default, set_mqpmo_default, set_mqod_default, set_mqsd_default};
use crate::mqmessage::{MQMessage, MQMD};
use crate::mqueuemanager::{MQueueManager, MQOpenOpts }; 
use crate::{ReasonCode, ComplCode};

pub struct MQueue<'a>{
    object: MQObject<'a>,
}
impl <'a>MQueue<'a> {
    pub fn builder() -> MQObjectBuilder<'a> {
        MQObjectBuilder::new()
    }
    pub fn close(&self){
        self.object.close();
    }
    pub fn put(&self, msg: &MQMessage, put_msg_opts: &MQPMO) -> Result<()> {
        self.object.put(msg, put_msg_opts)
    }
    pub fn get(&self, get_msg_opts: &MQGMO) -> Result<Option<String>> {
        self.object.get(get_msg_opts)
    }
    pub fn open(&mut self, open_options: MQOpenOpts)
                -> Result<()> {
        self.object.open(open_options)?;
        Ok(())
    }
}
pub struct MQTopic<'a>{
    object: MQObject<'a>,
    sub_handler: i32,
}
impl <'a, 'b>MQTopic<'a> {
    pub fn builder() -> MQObjectBuilder<'b> {
        MQObjectBuilder::new()
    }
    pub fn close(&self){
        self.object.close();
    }
    pub fn put(&self, msg: &MQMessage, put_msg_opts: &MQPMO) -> Result<()> {
        self.object.put(msg, put_msg_opts)
    }
    pub fn get(&self, get_msg_opts: &MQGMO) -> Result<Option<String>> {
        self.object.get(get_msg_opts)
    }
    pub fn open(&mut self, open_options: MQOpenOpts)
                -> Result<()> {
        self.object.open(open_options)?;
        Ok(())
    }

    pub fn subscribe(&self, sub_desc: &MQSD)
                     -> Result<()> {
        let mut cc = -1;
        let mut rc = -1;
        unsafe {
            mqi::MQSUB(*self.object.qm.conn_handler(),
                        sub_desc.as_ptr() as *mut c_void,
                        self.object.handler() as *const i32 as *mut i32,
                        &self.sub_handler as *const i32 as *mut i32,
                        &mut cc, &mut rc);
        };
        let cc = ComplCode(cc);
        let rc = ReasonCode(rc);
        if cc.is_ok() && rc.is_ok() {
            info!("Subscribed to topic {}", sub_desc.topic_name());
            Ok(())
        } else {
            error!("Error subscribing to topic {}", sub_desc.topic_name());
            Err(MQError::SubscribeFailed{ obj: sub_desc.topic_name(), cc, rc }.into())
        }
    }
}

#[derive(Clone, Debug)]
pub struct MQObject<'a> {
    name: String,
    handler: i32,
    mqod: mqi::MQOD,
    qm: &'a MQueueManager,
} 

impl <'a>MQObject<'a> {
    pub fn close(&self){
        let mut cc = -1;
        let mut rc = -1;
        unsafe {
            mqi::MQCLOSE(*self.qm.conn_handler(),
                          self.handler() as *const i32 as *mut i32,
                          mqi::MQCO_NONE, &mut cc,
                          &mut rc);
        };
        let cc = ComplCode(cc);
        let rc = ReasonCode(rc); 
        if cc.is_ok() && rc.is_ok() {
            info!("Closed queue {}", self.name);
        } else {
            panic!("Error closing queue {}, CC: {}, RC: {}", self.name, cc, rc);
        }
    }
    
    pub fn open(&mut self, open_options: MQOpenOpts) -> Result<()> {
        let mut cc = -1;
        let mut rc = -1;
        unsafe {
            mqi::MQOPEN(*self.qm.conn_handler(),
                         self.descriptor_ptr() as *mut c_void,
                         open_options.into(),
                         &mut self.handler as *mut i32,
                         &mut cc, &mut rc);
        };
        let cc = ComplCode(cc);
        let rc = ReasonCode(rc); 
        if cc.is_ok() && rc.is_ok() {
            info!("Opened object {:?}", self.name);
            Ok(())
        } else {
            error!("Error opening queue {:?}", self.name);
            Err(MQError::OpenFailed{ obj: self.name.clone(), cc, rc }.into())
        }
    }

    pub fn put(&self, msg: &MQMessage, put_msg_opts: &MQPMO) -> Result<()> {
        let mut cc = -1;
        let mut rc = -1;
        let data = msg.as_byte_vec();
        info!("Total length before PUT: {}", data.len());
        let mut mqmd = msg.get_descriptor().clone();
        unsafe {
            mqi::MQPUT(*self.qm.conn_handler(),
                       self.handler,
                       mqmd.as_mut_ptr()  as *mut c_void,
                       put_msg_opts.as_ptr() as *mut c_void,
                       data.len() as i32,
                       data.as_ptr() as *mut c_char as *mut c_void,
                       &mut cc, &mut rc)
        };
        let cc = ComplCode(cc);
        let rc = ReasonCode(rc);
        match cc.0 {
            mqi::MQCC_OK => {
                info!("Put OK: {}", msg.as_str()?);
                Ok(())
            },
            mqi::MQCC_FAILED => {
                Err(MQError::PutFailed{
                    msg: msg.as_string()?, cc, rc, obj: self.name.clone()
                }.into())
            },
            _ => Err(MQError::PutFailed{
                msg: msg.as_string()?, cc, rc, obj: self.name.clone()
                }.into()), 
        }
    }
    
    pub fn get(&self, get_msg_opts: &MQGMO) -> Result<Option<String>> {
        let mut cc = -1;
        let mut rc = -1;
        let mut msg_desr = MQMD::builder().build();
        let capacity: i32 = 1000;
        let mut buff: [u8;1000] = [0;1000];
        let mut data_length = 0;
        unsafe { 
            mqi::MQGET(*self.qm.conn_handler(), self.handler,
                       msg_desr.as_mut_ptr() as *mut c_void,
                       get_msg_opts.as_mut_ptr() as *mut c_void,
                       capacity,
                       // raw as *mut c_char as *mut c_void,
                       buff.as_mut_ptr() as *mut c_void,
                       &mut data_length, &mut cc, &mut rc);
        }
        let str_buf = std::str::from_utf8(&buff[0..data_length as usize])?;
        let cc = ComplCode(cc);
        let rc = ReasonCode(rc); 
        if rc.0 == mqi::MQRC_NO_MSG_AVAILABLE {
            Ok(None)
        } else { 
            match cc.0 {
                mqi::MQCC_OK => Ok(Some(str_buf.to_owned())),
                mqi::MQCC_WARNING => {
                    warn!("GET completed with {}, message begins with {}", rc, str_buf);
                    Ok(Some(str_buf.to_owned()))
                },
                mqi::MQCC_FAILED => {
                    Err(MQError::GetFailed{cc, rc, obj: self.name.clone()}.into())
                },
                _ => Err(MQError::GetFailed{cc, rc, obj: self.name.clone()}.into()),
            }
        }
    }
    pub fn handler(&self) -> &i32{
        &self.handler
    }
    pub fn descriptor_ptr(&self) -> *mut mqi::MQOD {
        &self.mqod as *const _ as *mut mqi::MQOD
    }
}

pub struct MQObjectBuilder<'a> {
    name: Option<String>,
    mqod: mqi::MQOD,
    handler: i32,
    qm: Option<&'a MQueueManager>,
}

impl <'a>MQObjectBuilder<'a> {
    fn new() -> MQObjectBuilder<'a>{
        let mqod = unsafe {
            let mut mqod: mqi::MQOD = std::mem::zeroed();
            set_mqod_default(&mut mqod as *mut mqi::MQOD);
            mqod
        };
        MQObjectBuilder {
            name: None,
            mqod,
            handler: mqi::MQHO_NONE,
            qm: None,
        }
    }
    pub fn version(&mut self, version: i32) -> &mut Self {
        self.mqod.Version = version;
        self
    }
    pub fn name(&mut self, name: &str) -> &mut Self {
        strcpy(name.as_bytes(), &mut self.mqod.ObjectName, name.len());
        self.name = Some(name.to_owned());
        self
    }
    pub fn queue_mgr(&mut self, qm: &'a MQueueManager) -> &mut Self{
        self.qm = Some(qm);
        strcpy(qm.name().as_bytes(), &mut self.mqod.ObjectQMgrName, qm.name().len());
        self
    }
    pub fn object_type(&mut self, mqot: i32) -> &mut Self {
        self.mqod.ObjectType = mqot;
        self
    }
    fn prepare_object(&self) -> Result<MQObject<'a>> {
        if self.name.is_none() || self.qm.is_none() {
            Err(MQError::QMNameNotSpecified.into())
        } else {
            let name = self.name.clone().unwrap();
            Ok(MQObject {
                name,
                mqod: self.mqod,
                handler: self.handler,
                qm: self.qm.unwrap(),
            })
        }
    }
    pub fn build_topic(&mut self) -> Result<MQTopic<'a>>{
        self.object_type(mqi::MQOT_TOPIC);
        let result_object = self.prepare_object()?;
        if self.mqod.ObjectType == mqi::MQOT_TOPIC {
            Ok(MQTopic{
                object: result_object,
                sub_handler: mqi::MQHO_NONE,
            })
        } else {
            Err(MQError::WrongObjectType { expected: "topic".to_owned(), actual: format!("obj_type: {}", self.mqod.ObjectType) }.into())
        }

    }
    pub fn build_queue(&mut self) -> Result<MQueue<'a>>{
        self.object_type(mqi::MQOT_Q);
        let result_object = self.prepare_object()?;
        if self.mqod.ObjectType == mqi::MQOT_Q {
            Ok(MQueue{
                object: result_object,
            })
        } else {
            Err(MQError::WrongObjectType { expected: "queue".to_owned(), actual: format!("obj_type: {}", self.mqod.ObjectType) }.into())
        }
    }
}

pub struct MQOD {
    mqod: mqi::MQOD    
}
impl MQOD {
    pub fn builder() -> MQODBuilder {
        MQODBuilder::new()
    }
    pub fn as_ptr(&self) -> *mut mqi::MQOD {
        &self.mqod as *const _ as *mut mqi::MQOD
    }
}

#[derive(Clone)]
pub struct MQODBuilder {
    mqod: mqi::MQOD
}

impl MQODBuilder {
    pub fn new() -> MQODBuilder {
        let mqod = unsafe {
            let mut mqod: mqi::MQOD = std::mem::zeroed();
            set_mqod_default(&mut mqod as *mut mqi::MQOD);
            mqod
        };
        MQODBuilder{ mqod }
    }
    pub fn build(&self) -> MQOD {
        MQOD { mqod: self.mqod }
    }
    pub fn version(&mut self, version: i32) -> &mut Self {
        self.mqod.Version = version;
        self
    }
    pub fn obj_name(&mut self, name: &str) -> &mut Self {
        strcpy(name.as_bytes(), &mut self.mqod.ObjectName, name.len());
        self
    }
    pub fn obj_qmgr_name(&mut self, name: &str) -> &mut Self {
        strcpy(name.as_bytes(), &mut self.mqod.ObjectQMgrName, name.len());
        self
    }
    pub fn obj_type(&mut self, mqot: i32) -> &mut Self {
        self.mqod.ObjectType = mqot;
        self
    }
}

pub struct MQPMO {
    mqpmo: mqi::MQPMO    
}

impl MQPMO {
    pub fn builder() -> MQPMOBuilder {
        MQPMOBuilder::new()
    }
    pub fn as_ptr(&self) -> *mut mqi::MQPMO {
        &self.mqpmo as *const _ as *mut mqi::MQPMO
    }
}


#[derive(Clone)]
pub struct MQPMOBuilder {
    mqpmo: mqi::MQPMO
}

impl MQPMOBuilder {
    pub fn new() -> MQPMOBuilder {
        let mqpmo = unsafe {
            let mut mqpmo: mqi::MQPMO = std::mem::zeroed();
            set_mqpmo_default(&mut mqpmo);
            mqpmo
        };
        MQPMOBuilder{mqpmo}
    }
    pub fn version(&mut self, version: i32) -> &mut Self {
        self.mqpmo.Version = version;
        self
    }
    pub fn with_option(&mut self, option: i32) -> &mut Self {
        self.mqpmo.Options += option;
        self
    } 
    pub fn build(&self) -> MQPMO {
        MQPMO { mqpmo: self.mqpmo }
    }
}

pub struct MQSD {
    mqsd: mqi::MQSD
}

impl MQSD {
    pub fn builder() -> MQSDBuilder {
        MQSDBuilder::new()
    }
    pub fn as_ptr(&self) -> *mut mqi::MQSD {
        &self.mqsd as *const _ as *mut mqi::MQSD
    }
    pub fn topic_name(&self) -> String {
        let mut dest = String::new();
        for c in &self.mqsd.ObjectName[..] {
            dest.push(*c as u8 as char);
        }
        dest
    }
}

pub struct MQSDBuilder {
    mqsd: mqi::MQSD,
}

impl MQSDBuilder {
    pub fn new() -> MQSDBuilder {
        let mqsd = unsafe {
            let mut mqsd: mqi::MQSD = std::mem::zeroed();
            set_mqsd_default(&mut mqsd);
            mqsd
        };
        MQSDBuilder{ mqsd }
    }
    pub fn build(&self) -> MQSD {
        MQSD { mqsd: self.mqsd }
    }
    pub fn topic_name(&mut self, name: &str) -> &mut Self {
        strcpy(name.as_bytes(), &mut self.mqsd.ObjectName, name.len());
        self
    }
    pub fn topic_string(&mut self, name: &str) -> &mut Self {
        self.mqsd.ObjectString.VSPtr = name.as_ptr() as *mut c_void;
        self.mqsd.ObjectString.VSLength = mqi::MQVS_NULL_TERMINATED;
        self
    }
    pub fn res_topic_string(&mut self, name: &str) -> &mut Self {
        self.mqsd.ResObjectString.VSPtr = name.as_ptr() as *mut c_void;
        self.mqsd.ResObjectString.VSLength = mqi::MQVS_NULL_TERMINATED;
        self
    }
    pub fn with_option(&mut self, option: i32) -> &mut Self {
        self.mqsd.Options += option;
        self
    }
}

pub struct MQGMO {
    mqgmo: mqi::MQGMO
}

impl MQGMO {
    pub fn builder() -> MQGMOBuilder {
        MQGMOBuilder::new()
    }
    pub fn as_mut_ptr(&self) -> *mut mqi::MQGMO {
        &self.mqgmo as *const _ as *mut mqi::MQGMO
    }
}

#[derive(Clone)]
pub struct MQGMOBuilder {
    mqgmo: mqi::MQGMO
}

impl MQGMOBuilder {
    pub fn new() -> MQGMOBuilder {
        let mqgmo = unsafe {
            let mut mqgmo: mqi::MQGMO = std::mem::zeroed();
            set_mqgmo_default(&mut mqgmo);
            mqgmo
        };
        MQGMOBuilder{ mqgmo }
    }
    pub fn build(&self) -> MQGMO {
        MQGMO{mqgmo: self.mqgmo}
    }
    pub fn wait(&mut self, wait: bool) -> &mut Self{
        if wait {
            self.mqgmo.Options += mqi::MQGMO_WAIT;
        } else {
            self.mqgmo.Options += mqi::MQGMO_NO_WAIT;
        }
        self
    }
    pub fn accept_truncated_msg(&mut self, accept: bool) -> &mut Self {
        if accept {
            self.mqgmo.Options += mqi::MQGMO_ACCEPT_TRUNCATED_MSG;
        }
        self
    }
    pub fn wait_interval(&mut self, ms: i32) -> &mut Self{
        self.with_option(mqi::MQGMO_WAIT);
        self.mqgmo.WaitInterval = ms;
        self
    }
    pub fn with_option(&mut self, option: i32) -> &mut Self {
        self.mqgmo.Options += option;
        self
    }
}
